package test;

import java.util.Stack;

/**
 * @author sunzhuokun
 * @date 10:52 2024/5/11
 * @description LISP语言唯一的语法就是括号要配对
 * 形如 (OP P1 P2 …)，括号内元素由单个空格分割。
 * 其中第一个元素OP为操作符，后续元素均为其参数，参数个数取决于操作符类型
 * 注意：参数 P1, P2 也有可能是另外一个嵌套的 (OP P1 P2 …)
 * 当前OP类型为add/sub/mul/div(全小写)，分别代表整数的加减乘除法。简单起见，所以OP参数个数为2
 * 举例
 * 输入：(mul 3 -7)输出：-21
 * 输入：(add 1 2) 输出：3
 * 输入：(sub (mul 2 4) (div 9 3)) 输出 ：5
 * 输入：(div 1 0) 输出：error
 * 输入描述：
 * 合法C字符串，字符串长度不超过512，用例保证了无语法错误
 * 输出描述：
 * 合法C字符串，字符包括’0’-‘9’及负号’-‘或者’error’
 */
public class LISP {

    public static void main(String[] args) {
        String input = "(mul 3 -7)";
        String input1 = "(add 1 2)";
        String input2 = "(add (sub (mul 2 4) (div 9 3)) 3)";
        String input3 = "(div 1 0)";
        String input4 = "(sub (mul 2 4) (div 9 3))";
//        System.out.println(eval(input2));
        eval2(input4);
    }

    private static void eval2(String input) {
        Stack<String> opStack = new Stack<>();
        Stack<Integer> dataStack = new Stack<>();
        try {
            exec(input, opStack, dataStack);
        } catch (Exception e) {
            System.out.println("error");
        }
    }


    /**
     * 1、从头开始遍历字符，遇到左括号，就将其后边的三个操作符压入操作符栈中
     * 2、遇到数字，就将其压入数据栈中（考虑连续数字和负数）
     * 3、遇到右括号，就从操作符栈中出栈最近的操作符
     * 4、然后在数据栈中出栈两个数字
     * 5、通过第3步的操作符和第4步的两个数字进行计算
     * 6、将第5步的计算结果压入数据栈中
     * 7、重复执行上述1~6，直到操作符栈为空，输出数据栈的结果即可
     */
    private static void exec(String input, Stack<String> opStack, Stack<Integer> dataStack) {
        int len = input.length();
        for (int i = 0; i < len; ) {
            char c = input.charAt(i);
            if (c == ' ') {
                i++;
                continue;
            }
            if (c == '(') {
                opStack.push(input.substring(i + 1, i + 4));
                i = i + 4;
                continue;
            }
            if (c == '-' || Character.isDigit(c)) {
                int j = i;
                while (Character.isDigit(input.charAt(j + 1))) {
                    j++;
                }
                dataStack.push(Integer.parseInt(input.substring(i, j + 1)));
                i = j + 1;
                continue;
            }
            if (c == ')') {
                String op = opStack.pop();
                Integer p2 = dataStack.pop();
                Integer p1 = dataStack.pop();
                Integer res = calculate(op, p1, p2);
                dataStack.push(res);
                i++;
            }
        }
        if (opStack.isEmpty()) {
            System.out.println(dataStack.pop());
        } else {
            System.out.println("error");
        }
    }

    private static Integer calculate(String op, Integer p1, Integer p2) {
        switch (op) {
            case "add":
                return p1 + p2;
            case "sub":
                return p1 - p2;
            case "mul":
                return p1 * p2;
            case "div":
                return p1 / p2;
            default:
                throw new RuntimeException();
        }
    }

    private static String eval(String input) {
        if (input == null || input.length() == 0 || (!input.startsWith("(") && !input.endsWith(")"))) {
            return "error";
        }
        String computeStr = input.substring(1, input.length() - 1);
        String op = computeStr.substring(0, computeStr.indexOf(" "));
        String paramStr = computeStr.substring(computeStr.indexOf(" ") + 1);

        String firstParamStr = paramStr.startsWith("(") ? paramStr.substring(0, paramStr.lastIndexOf(" ")) :
                paramStr.substring(0, paramStr.indexOf(" "));
        String secondParamStr = paramStr.startsWith("(") ? paramStr.substring(paramStr.lastIndexOf(" ") + 1) :
                paramStr.substring(paramStr.indexOf(" ") + 1);
        Integer firstParam = isLisp(firstParamStr) ? Integer.parseInt(eval(firstParamStr)) :
                Integer.parseInt(firstParamStr);
        Integer secondParam = isLisp(secondParamStr) ? Integer.parseInt(eval(secondParamStr)) :
                Integer.parseInt(secondParamStr);

        switch (op) {
            case "add":
                return String.valueOf(firstParam + secondParam);
            case "sub":
                return String.valueOf(firstParam - secondParam);
            case "mul":
                return String.valueOf(firstParam * secondParam);
            case "div":
                if (secondParam == 0) {
                    return "error";
                } else {
                    return String.valueOf(firstParam / secondParam);
                }
            default:
                return "error";
        }
    }

    public static boolean isLisp(String input) {
        if (input == null || input.length() == 0 || (!input.startsWith("(") && !input.endsWith(")"))) {
            return false;
        }
        return true;
    }
}
